package cn.lili.modules.kit.plugin.wechat;

import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.lili.cache.Cache;
import cn.lili.cache.CachePrefix;
import cn.lili.common.context.ThreadContextHolder;
import cn.lili.common.enums.ResultCode;
import cn.lili.common.event.TransactionCommitSendMQEvent;
import cn.lili.common.exception.ServiceException;
import cn.lili.common.properties.ApiProperties;
import cn.lili.common.security.context.UserContext;
import cn.lili.common.security.enums.SceneEnums;
import cn.lili.common.thread.ThreadConfig;
import cn.lili.common.utils.*;
import cn.lili.common.vo.ResultMessage;
import cn.lili.exchange.AmqpExchangeProperties;
import cn.lili.modules.kit.Payment;
import cn.lili.modules.kit.core.PaymentHttpResponse;
import cn.lili.modules.kit.core.enums.RequestMethodEnums;
import cn.lili.modules.kit.core.enums.SignType;
import cn.lili.modules.kit.core.kit.*;
import cn.lili.modules.kit.core.utils.DateTimeZoneUtil;
import cn.lili.modules.kit.plugin.wechat.model.CombineQueryResult;
import cn.lili.modules.kit.plugin.wechat.model.CombineTransactionsRequest;
import cn.lili.modules.kit.plugin.wechat.model.PartnerTransactionsRequest;
import cn.lili.modules.kit.plugin.wechat.model.PartnerTransactionsResult;
import cn.lili.modules.order.order.client.OrderClient;
import cn.lili.modules.payment.entity.dos.*;
import cn.lili.modules.payment.entity.dto.*;
import cn.lili.modules.payment.entity.enums.PaymentClientEnum;
import cn.lili.modules.payment.entity.enums.PaymentMethodEnum;
import cn.lili.modules.payment.entity.vo.OutOrderLogVO;
import cn.lili.modules.payment.service.*;
import cn.lili.modules.payment.util.EcommerceUtil;
import cn.lili.modules.payment.wechat.enums.WechatApiEnum;
import cn.lili.modules.payment.wechat.enums.WechatDomain;
import cn.lili.modules.payment.wechat.model.Amount;
import cn.lili.modules.payment.wechat.model.RefundModel;
import cn.lili.modules.payment.wechat.model.profitsharing.ProfitSharing;
import cn.lili.modules.payment.wechat.model.profitsharing.ProfitSharingResponse;
import cn.lili.modules.payment.wechat.model.profitsharing.Receiver;
import cn.lili.modules.payment.wechat.model.profitsharing.ReceiverResponse;
import cn.lili.modules.system.client.SettingClient;
import cn.lili.modules.system.entity.dos.Setting;
import cn.lili.modules.system.entity.dto.payment.WechatPaymentSetting;
import cn.lili.modules.system.entity.enums.SettingEnum;
import cn.lili.routing.PaymentRoutingKey;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * 微信支付
 *
 * @author Chopper
 * @since 2020/12/21 17:44
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class WechatPlugin implements Payment {

    private final Cache<String> cache;

    private final SettingClient settingClient;

    private final ApiProperties apiProperties;

    private final PaymentLogService paymentLogService;

    private final CombinePaymentLogService combinePaymentLogService;

    private final RefundLogService refundLogService;

    private final SubMerchantInformationService subMerchantInformationService;

    private final ApplicationEventPublisher applicationEventPublisher;

    private final AmqpExchangeProperties amqpExchangeProperties;

    private final EcommerceUtil ecommerceUtil;

    private final OutOrderLogService outOrderLogService;

    private final OutOrderDetailLogService outOrderDetailLogService;

    private final IPaymentCallbackLogService paymentCallbackLogService;

    private final ThreadConfig threadConfig;

    private final OrderClient orderClient;
    /**
     * 微信小程序支付
     * 合单小程序下单API 文档地址: <a href="https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_3_4.shtml">...</a>
     * 小程序下单 文档地址：文档地址: <a href="https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_2_3.shtml">...</a>
     *
     * @param paymentWakeupParam api参数
     * @return 唤起支付参数
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResultMessage<Object> mpPay(PaymentWakeupParam paymentWakeupParam) {

        try {
            WechatPaymentSetting setting = wechatPaymentSetting();
            String responseBody = this.requestWechatAPI(paymentWakeupParam);
            String prepayId = JSONUtil.parseObj(responseBody).getStr("prepay_id");
            Map<String, String> map = WxPayKit.jsApiCreateSign(setting.getMpAppId(), prepayId, setting.getApiclient_key());
            return ResultUtil.data(map);
        } catch (ServiceException serviceException) {
            throw serviceException;
        } catch (Exception e) {
            log.error("支付异常", e);
            throw new ServiceException(ResultCode.PAY_ERROR);
        }

    }

    /**
     * 合单Native下单API
     * 合单Native下单API 文档地址: <a href="https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_3_5.shtml">...</a>
     * Native下单API 文档地址：文档地址: <a href="https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_2_12.shtml">...</a>
     *
     * @param paymentWakeupParam api参数
     * @return 唤起支付参数
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResultMessage<Object> nativePay(PaymentWakeupParam paymentWakeupParam) {
        try {
            JSONObject jsonObject = new JSONObject(this.requestWechatAPI(paymentWakeupParam));
            if (jsonObject.containsKey("code_url")) {
                return ResultUtil.data(jsonObject.getStr("code_url"));
            } else {
                throw new ServiceException(jsonObject.getStr("message"));
            }
        } catch (ServiceException e) {
            throw e;
        } catch (Exception e) {
            log.error("支付配置异常", e);
            throw new ServiceException(ResultCode.PAY_ERROR, "支付参数配置异常问题，请联系管理员");
        }
    }

    /**
     * APP调起支付API
     * 合单APP下单API 文档地址: <a href="https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_3_1.shtml">...</a>
     * APP调起支付API 文档地址：<a href="https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_3_6.shtml">...</a>
     *
     * @param paymentWakeupParam api参数
     * @return 返回调起支付参数
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResultMessage<Object> appPay(PaymentWakeupParam paymentWakeupParam) {
        //判断是交易支付还是订单支付
        //如果是交易支付则调用合单支付，如果是订单支付则调用微信支付
        try {
            WechatPaymentSetting setting = wechatPaymentSetting();
            JSONObject jsonObject = JSONUtil.parseObj(this.requestWechatAPI(paymentWakeupParam));
            String prepayId = jsonObject.getStr("prepay_id");
            Map<String, String> map = WxPayKit.appPrepayIdCreateSign(setting.getAppId(), setting.getMchId(), prepayId, setting.getApiclient_key(),
                    SignType.MD5);
            return ResultUtil.data(map);
        } catch (ServiceException serviceException) {
            throw serviceException;
        } catch (Exception e) {
            log.error("支付异常", e);
            throw new ServiceException(ResultCode.PAY_ERROR);
        }
    }

    /**
     * JSAPI下单API
     * 合单JSAPI下单API 文档地址: <a href="https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_3_3.shtml">...</a>
     * JSAPI下单API 文档地址：文档地址: <a href="https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_2_2.shtml">...</a>
     *
     * @param paymentWakeupParam api参数
     * @return 唤起支付参数
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResultMessage<Object> jsApiPay(PaymentWakeupParam paymentWakeupParam) {

        //判断是交易支付还是订单支付
        //如果是交易支付则调用合单支付，如果是订单支付则调用微信支付
        try {
            WechatPaymentSetting setting = wechatPaymentSetting();
            String prepayId = JSONUtil.parseObj(this.requestWechatAPI(paymentWakeupParam)).getStr("prepay_id");
            Map<String, String> map = WxPayKit.jsApiCreateSign(setting.getMpAppId(), prepayId, setting.getApiclient_key());
            log.info("唤起支付参数:{}", map);
            return ResultUtil.data(map);
        } catch (ServiceException serviceException) {
            throw serviceException;
        } catch (Exception e) {
            log.error("支付异常", e);
            throw new ServiceException(ResultCode.PAY_ERROR);
        }
    }

    /**
     * H5下单API
     * 合单JSAPI下单API 文档地址: <a href="https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_3_2.shtml">...</a>
     * H5下单API 文档地址：文档地址: <a href="https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_2_4.shtml">...</a>
     *
     * @param paymentWakeupParam api参数
     * @return 唤起支付参数
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResultMessage<Object> h5pay(PaymentWakeupParam paymentWakeupParam) {

        //判断是交易支付还是订单支付
        //如果是交易支付则调用合单支付，如果是订单支付则调用微信支付
        try {
            return ResultUtil.data(JSONUtil.toJsonStr(this.requestWechatAPI(paymentWakeupParam)));
        } catch (ServiceException serviceException) {
            throw serviceException;
        } catch (Exception e) {
            log.error("支付异常", e);
            throw new ServiceException(ResultCode.PAY_ERROR);
        }

    }

    /**
     * 支付回调
     */
    @Override
    public void callBack() throws Exception {
        verifyNotify();
    }

    /**
     * 支付通知
     */
    @Override
    public void paymentNotify() {
        try {
            verifyNotify();
        } catch (ServiceException serviceException) {
            throw serviceException;
        } catch (Exception e) {
            log.error("支付异常", e);
        }
    }

    @Override
    public void refundNotify() {
        try {
            //获取通知明文
            String plainText = getPlaintext();
            log.info("微信退款通知明文 {}", plainText);

            JSONObject jsonObject = JSONUtil.parseObj(plainText);

            if (("SUCCESS").equals(jsonObject.getStr("refund_status"))) {
                log.info("退款成功 {}", plainText);
                //校验服务器端响应
                String outTradeNo = jsonObject.getStr("out_refund_no");
                String refundId = jsonObject.getStr("refund_id");

                RefundLog refundLog = refundLogService.getByOutOrderNo(outTradeNo);

                if (refundLog != null) {
                    refundLog.setIsRefund(true);
                    refundLog.setRefundTransactionId(refundId);
                    refundLogService.updateById(refundLog);
                }

            } else {
                log.info("退款失败 {}", plainText);
                String outTradeNo = jsonObject.getStr("out_refund_no");
                String refundId = jsonObject.getStr("refund_id");


                RefundLog refundLog = refundLogService.getByOutOrderNo(outTradeNo);

                if (refundLog != null) {
                    refundLog.setRefundTransactionId(refundId);
                    refundLog.setErrorMessage("可前往【商户平台—>交易中心】，手动处理此笔退款");
                    refundLogService.updateById(refundLog);
                }
            }
        } catch (ServiceException serviceException) {
            throw serviceException;
        } catch (Exception e) {
            log.error("微信退款失败", e);
        }
    }


    /**
     * 查询支付结果
     *
     * @param paymentLog 支付日志
     */
    @Override
    @Transactional
    public void checkPaymentResult(PaymentLog paymentLog) {
        WechatPaymentSetting setting = wechatPaymentSetting();
        try {
            String url =
                    String.format(WechatApiEnum.ORDER_QUERY_BY_NO.toString(), paymentLog.getOutTradeNo()) + "?mchid=" + setting.getMchId();

            PaymentHttpResponse response = WechatApi.v3(RequestMethodEnums.GET, WechatDomain.CHINA.toString(), url, setting.getMchId(),
                    setting.getSerialNumber(), null, setting.getApiclient_key(), "");

            log.info("查询支付结果响应 {}", JSONUtil.toJsonStr(response));
            if (WxPayKit.verifySignature(response, getPlatformCert())) {

                String body = response.getBody();

                PartnerTransactionsResult result = JSONUtil.toBean(body, PartnerTransactionsResult.class);

                this.normPaymentResultHandler(result, paymentLog);

            } else {
                log.error("微信支付回调验签失败");
            }
        } catch (ServiceException serviceException) {
            log.error("支付回查异常", serviceException);
            throw serviceException;
        } catch (Exception e) {
            log.error("微信支付异常", e);
            throw new ServiceException("微信支付异常");
        }
    }

    /**
     * 查询支付结果
     *
     * @param combinePaymentLog 合单支付日志
     */
    @Override
    @Transactional
    public void checkPaymentResult(CombinePaymentLog combinePaymentLog) {

        //支付设置
        WechatPaymentSetting setting = wechatPaymentSetting();
        String url = String.format(WechatApiEnum.COMBINE_TRANSACTIONS_QUERY.toString(), combinePaymentLog.getCombineOutTradeNo());

        log.info("查询支付结果 {}", JSONUtil.toJsonStr(combinePaymentLog.getPaymentRequestSource()));
        PaymentHttpResponse response = null;
        try {
            response = WechatApi.v3(RequestMethodEnums.GET, WechatDomain.CHINA.toString(), url, setting.getMchId(), setting.getSerialNumber(), null
                    , setting.getApiclient_key(), "");
        } catch (Exception e) {
            log.error("微信支付异常：", e);
            throw new ServiceException(ResultCode.PAY_ERROR);
        }

        //验签
        if (!WxPayKit.verifySignature(response, getPlatformCert())) {
            throw new ServiceException("微信支付回调验签失败");
        }

        //获取响应体
        String body = response.getBody();

        //合单支付主动查询结果
        CombineQueryResult combineQueryResult = GsonUtils.fromJson(body, CombineQueryResult.class);

        this.combinePaymentResultHandler(combineQueryResult, combinePaymentLog);
    }


    @Override
    public void refund(RefundLog refundLog) {
        try {


            Amount amount = new Amount().setRefund(CurrencyUtil.fen(refundLog.getPrice())).setTotal(CurrencyUtil.fen(refundLog.getPayPrice()));

            WechatPaymentSetting setting = wechatPaymentSetting();


            PaymentLog paymentLog = paymentLogService.getPaymentLogByOrderSN(refundLog.getOrderSn());
            if (paymentLog == null) {
                throw new ServiceException(ResultCode.PAY_NOT_EXIST_ORDER);
            }

            //退款参数准备
            RefundModel refundModel =
                    new RefundModel().setTransaction_id(refundLog.getTransactionId()).setOut_refund_no(refundLog.getOutRefundNo()).setReason(refundLog.getRefundReason()).setAmount(amount).setSub_mchid(paymentLog.getMchId()).setSp_appid(setting.getAppId()).setNotify_url(refundNotifyUrl(apiProperties.getBuyer(), PaymentMethodEnum.WECHAT));
            log.info("微信退款参数 {}", JSONUtil.toJsonStr(refundModel));
            PaymentHttpResponse response = WechatApi.v3(RequestMethodEnums.POST, WechatDomain.CHINA.toString(),
                    WechatApiEnum.E_COMMERCE_REFUNDS.toString(), setting.getMchId(), setting.getSerialNumber(), null, setting.getApiclient_key(),
                    JSONUtil.toJsonStr(refundModel));
            log.info("微信退款响应 {}", response);
            //退款申请成功
            if (response.getStatus() == 200) {
                refundLog.setIsRefund(true);
                //非同步退款所以 200状态不做处理
            } else {
                //退款申请失败
                refundLog.setErrorMessage(response.getBody());
            }

        } catch (ServiceException serviceException) {
            throw serviceException;
        } catch (Exception e) {
            log.error("微信退款申请失败", e);
        }

    }


    @Override
    public OutOrderLogVO profitSharing(ProfitSharingRequestDTO profitSharingRequestDTO) {

        WechatPaymentSetting setting = wechatPaymentSetting();

        //获取支付日志
        PaymentLog paymentLog = paymentLogService.getPaymentLogByOrderSN(profitSharingRequestDTO.getOrderSn());
        if (paymentLog == null) {
            orderClient.updateProfitSharingOrderFlow(profitSharingRequestDTO.getOrderSn());
            orderClient.addOrderFlowProfitSharing(profitSharingRequestDTO.getOrderSn(),"支付日志不存在");
            throw new ServiceException(ResultCode.PROFIT_SHARING_ERROR, "支付日志不存在");
        }
        //出资人ID
        String contributorId = profitSharingRequestDTO.getStoreId();

        String contributorSubMchid = subMerchantInformationService.getSubMerchantInformationByUserId(contributorId,
                profitSharingRequestDTO.getPaymentMethodEnum()).getSubMerchantId();
        if (CharSequenceUtil.isBlank(contributorId) || CharSequenceUtil.isBlank(contributorSubMchid)) {
            orderClient.updateProfitSharingOrderFlow(profitSharingRequestDTO.getOrderSn());
            orderClient.addOrderFlowProfitSharing(profitSharingRequestDTO.getOrderSn(),"收款人ID为空，如果修改过商户二级商户ID，请联系开发手动处理");
            throw new ServiceException(ResultCode.PROFIT_SHARING_ERROR, "收款人ID为空，如果修改过商户二级商户ID，请联系开发手动处理");
        }


        //填写分账参数
        ProfitSharing profitSharing = new ProfitSharing();
        profitSharing.setSub_mchid(contributorSubMchid);
        profitSharing.setOut_order_no(SnowFlake.createStr("P"));
        profitSharing.setTransaction_id(paymentLog.getTransactionId());

        //填写分账接收方信息
        List<Receiver> receivers = new ArrayList<>();

        //保存分账子单记录
        List<OutOrderDetailLog> outOrderDetailLogs = new ArrayList<>();
        for (OutOrderItem outOrderItem : profitSharingRequestDTO.getOutOrderItems()) {

            //如果收款用户与分账用户id一致 则 跳过
            if (profitSharingRequestDTO.getStoreId().equals(outOrderItem.getUserId())) {
                continue;
            }
            //平台收款
            if (outOrderItem.getScene() == SceneEnums.SYSTEM) {
                receivers.add(new Receiver(setting.getSubMchId(), outOrderItem.getPrice(),
                        outOrderItem.generateDescription()));
            } else {
                //其他人收款
                receivers.add(new Receiver(subMerchantInformationService.getSubMerchantInformationByUserId(outOrderItem.getUserId(),
                        profitSharingRequestDTO.getPaymentMethodEnum()).getSubMerchantId(), outOrderItem.getPrice(),
                        outOrderItem.generateDescription()));
            }
        }

        profitSharing.setReceivers(receivers);
        profitSharing.setFinish(true);

        //请求分账
        JSONObject jsonObject = ecommerceUtil.profitSharing(profitSharing);
        ProfitSharingResponse profitSharingResponse = JSONUtil.toBean(jsonObject, ProfitSharingResponse.class);

        //记录分账日志
        OutOrderLog outOrderLog = outOrderLogService.getByOrderSn(profitSharingRequestDTO.getOrderSn());
        if (null == outOrderLog) {
            outOrderLog = new OutOrderLog(profitSharingRequestDTO.getOrderSn(), profitSharingRequestDTO.getTotalAmount(),
                    profitSharingResponse);

            outOrderLog.setPaymentMethod(PaymentMethodEnum.WECHAT.name());
            outOrderLogService.save(outOrderLog);
        }

        for (ReceiverResponse receiverResponse : profitSharingResponse.getReceivers()) {
            OutOrderDetailLog outOrderDetailLog = new OutOrderDetailLog(outOrderLog, receiverResponse);
            outOrderDetailLogs.add(outOrderDetailLog);
        }

        //保存分账子账单信息
        outOrderDetailLogService.saveBatch(outOrderDetailLogs);

        return new OutOrderLogVO(outOrderLog, outOrderDetailLogs);
    }

    @Override
    public OutOrderLog profitSharingPlatform(PaymentLog paymentLog) {

        WechatPaymentSetting setting = wechatPaymentSetting();

        //填写分账参数
        ProfitSharing profitSharing = new ProfitSharing();
        profitSharing.setSub_mchid(setting.getSubMchId());
        profitSharing.setOut_order_no(SnowFlake.createStr("P"));
        profitSharing.setTransaction_id(paymentLog.getTransactionId());

        //填写分账接收方信息
        List<Receiver> receivers = new ArrayList<>();

        receivers.add(new Receiver(setting.getSubMchId(), CurrencyUtil.mul(paymentLog.getPrice(), 100),
                OutOrderDetailLog.generateSubsidiesToPlatform(paymentLog.getOrderSn(), paymentLog.getPrice())));


        profitSharing.setReceivers(receivers);
        profitSharing.setFinish(true);

        //请求分账
        JSONObject jsonObject = ecommerceUtil.profitSharing(profitSharing);
        ProfitSharingResponse profitSharingResponse = JSONUtil.toBean(jsonObject, ProfitSharingResponse.class);

        //记录分账日志
        OutOrderLog outOrderLog = outOrderLogService.getByOrderSn(paymentLog.getOrderSn());
        if (null == outOrderLog) {
            outOrderLog = new OutOrderLog(paymentLog.getOrderSn(), paymentLog.getPrice(), profitSharingResponse);
            outOrderLogService.save(outOrderLog);
        }

        //保存分账子单记录
        for (ReceiverResponse receiverResponse : profitSharingResponse.getReceivers()) {
            //保存分账子账单信息
            outOrderDetailLogService.save(new OutOrderDetailLog(outOrderLog, receiverResponse));
        }

        return outOrderLog;
    }

    /**
     * 验证回调执行方法
     */
    private void verifyNotify() throws Exception {

        //获取通知明文
        String plainText = getPlaintext();
        log.info("微信支付通知明文 {}", plainText);
        JSONObject jsonObject = JSONUtil.parseObj(plainText);
        /** 保存微信回调记录**/
        Objects.requireNonNull(threadConfig.getAsyncExecutor()).execute(() -> {
            PartnerTransactionsResult transactionsResult = GsonUtils.fromJson(plainText, PartnerTransactionsResult.class);
            PaymentLog paymentLog = paymentLogService.getPaymentLogByOutTradeNo(transactionsResult.getOutTradeNo());
            //保存支付回调通知结果日志
            PaymentCallbackLog paymentCallbackLog = new PaymentCallbackLog();
            paymentCallbackLog.setPassbackParams("WHCHAT_PAY");
            paymentCallbackLog.setOutTradeNo(transactionsResult != null && StrUtil.isNotEmpty(transactionsResult.getOutTradeNo()) ? transactionsResult.getOutTradeNo() : "");
            paymentCallbackLog.setPaycallbackObj(plainText);
            paymentCallbackLog.setExecResult(paymentLog != null && StrUtil.isNotEmpty(paymentLog.getOrderSn()) ? ("orderSn:" + paymentLog.getOrderSn()) : "");
            paymentCallbackLog.setMethodName("WechatPlugin.verifyNotify");
            paymentCallbackLogService.save(paymentCallbackLog);
        });

        //是否合单支付
        Boolean isCombine = jsonObject.containsKey("combine_out_trade_no");

        if (Boolean.TRUE.equals(isCombine)) {

            //合单支付主动查询结果
            CombineQueryResult combineQueryResult = GsonUtils.fromJson(plainText, CombineQueryResult.class);

            //合单支付
            combinePaymentResultHandler(combineQueryResult);
        } else {
            //合单支付主动查询结果
            PartnerTransactionsResult partnerTransactionsResult = GsonUtils.fromJson(plainText, PartnerTransactionsResult.class);

            //普通支付
            normPaymentResultHandler(partnerTransactionsResult);
        }
    }


    /**
     * 普通支付结果处理
     *
     * @param partnerTransactionsResult 支付结果
     */
    private void normPaymentResultHandler(PartnerTransactionsResult partnerTransactionsResult) {

        this.normPaymentResultHandler(partnerTransactionsResult,
                paymentLogService.getPaymentLogByOutTradeNo(partnerTransactionsResult.getOutTradeNo()));
    }

    /**
     * 普通支付结果处理
     *
     * @param paymentLog                支付日志
     * @param partnerTransactionsResult 支付结果
     */
    private void normPaymentResultHandler(PartnerTransactionsResult partnerTransactionsResult, PaymentLog paymentLog) {

        //写入主动查询结果
        paymentLog.setPaymentResponseSource(JSONUtil.toJsonStr(partnerTransactionsResult));

        String paymentResult = partnerTransactionsResult.getTradeState();
        //支付成功则写入
        if (paymentResult.equals("SUCCESS")) {
            paymentLog.setTransactionId(partnerTransactionsResult.getTransactionId());
            paymentLog.setOutTradeNo(partnerTransactionsResult.getOutTradeNo());
            paymentLog.setPrice(CurrencyUtil.div(partnerTransactionsResult.getAmount().getTotal(), 100));
            paymentLog.setIsPay(true);
            //标记已检查
            paymentLog.setIsCheck(true);
        }
        //支付中则先返回
        else if (paymentResult.equals("USERPAYING")) {
            //标记需要再次查询
            paymentLog.setIsCheck(false);
            paymentLog.setPaymentResponseSource("交易中，等待回传");

        } else if (paymentResult.equals("CLOSED")) {
            //标记已校验
            paymentLog.setIsCheck(true);
        } else {
            paymentLog.setIsCheck(false);
        }
        paymentLogService.updateById(paymentLog);

        // 支付状态为TRUE才进行通知
        if (paymentLog.getIsPay().equals(true)) {
            PaymentCallback paymentCallback = PaymentCallback.builder().paymentLog(paymentLog).isCombine(false).build();
            log.info("普通支付回调业务");
            applicationEventPublisher.publishEvent(TransactionCommitSendMQEvent.builder().source("普通支付回调业务").exchange(amqpExchangeProperties.getPayment()).routingKey(PaymentRoutingKey.PAYMENT_CALLBACK).message(paymentCallback).build());
        }
    }

    /**
     * 合单支付 结果通知/回调查询 处理lll
     *
     * @param combineQueryResult 合单支付结果
     * @param combinePaymentLog  合单支付日志
     */
    private void combinePaymentResultHandler(CombineQueryResult combineQueryResult, CombinePaymentLog combinePaymentLog) {


        //写入主动查询结果
        combinePaymentLog.setPaymentResponseSource(JSONUtil.toJsonStr(combineQueryResult));

        //判定支付结果状态，如果是已关闭或者已支付，则标记合并支付单已校验
        List<CombineQueryResult.SubOrders> subOrders =
                combineQueryResult.getSubOrders().stream().filter(obj -> obj.getTradeState().equals("CLOSED") || obj.getTradeState().equals(
                        "SUCCESS")).toList();

        //如果子单全部为关闭或者支付成功，则标记合单支付状态为已完成
        if (subOrders.size() == combineQueryResult.getSubOrders().size()) {
            combinePaymentLog.setIsCheck(true);
        } else {
            throw new ServiceException("合单支付状态未完成:" + combineQueryResult.getCombineOutTradeNo());
        }


        //获取子单支付日志
        List<PaymentLog> paymentLogs = paymentLogService.getPaymentLogByCombineOutTradeNo(combinePaymentLog.getCombineOutTradeNo());

        for (PaymentLog paymentLog : paymentLogs) {
            for (CombineQueryResult.SubOrders subOrder : combineQueryResult.getSubOrders()) {


                // 如果子单支付日志的订单号和查询结果的订单号一致
                if (subOrder.getOutTradeNo().equals(paymentLog.getOutTradeNo())) {


                    //如果子单支付日志的交易状态为成功
                    if (subOrder.getTradeState().equals("SUCCESS")) {
                        paymentLog.setIsCheck(true);
                        paymentLog.setIsPay(true);
                        paymentLog.setPrice(CurrencyUtil.reversalFen(subOrder.getAmount().getTotalAmount()));
                        paymentLog.setTransactionId(subOrder.getTransactionId());
                        paymentLog.setOutTradeNo(subOrder.getOutTradeNo());
                    } else {
                        paymentLog.setIsCheck(true);
                        paymentLog.setIsPay(false);
                        paymentLog.setPrice(CurrencyUtil.reversalFen(subOrder.getAmount().getTotalAmount()));
                        paymentLog.setOutTradeNo(subOrder.getOutTradeNo());
                        paymentLog.setTransactionId(subOrder.getTransactionId());
                    }
                }
            }
        }

        paymentLogService.updateBatchById(paymentLogs);
        combinePaymentLogService.updateById(combinePaymentLog);

        // 支付状态为TRUE才进行通知
        if (paymentLogs.get(0).getIsPay().equals(true)) {
            PaymentCallback paymentCallback =
                    PaymentCallback.builder().paymentLogs(paymentLogs).combinePaymentLog(combinePaymentLog).isCombine(true).build();

            log.info("合单支付回调业务");
            applicationEventPublisher.publishEvent(TransactionCommitSendMQEvent.builder().source("合单支付回调业务").exchange(amqpExchangeProperties.getPayment()).routingKey(PaymentRoutingKey.PAYMENT_CALLBACK).message(paymentCallback).build());
        }
    }

    /**
     * 合单支付 结果通知/回调查询 处理
     *
     * @param combineQueryResult 合单支付结果
     */
    private void combinePaymentResultHandler(CombineQueryResult combineQueryResult) {
        combinePaymentResultHandler(combineQueryResult,
                combinePaymentLogService.getCombinePaymentLogByCombineOutTradeNo(combineQueryResult.getCombineOutTradeNo()));
    }


    /**
     * 获取通知明文
     *
     * @return 微信通知明文
     * @throws Exception 支付异常
     */
    private String getPlaintext() throws Exception {

        HttpServletRequest request = ThreadContextHolder.getHttpRequest();
        String timestamp = request.getHeader("Wechatpay-Timestamp");
        String nonce = request.getHeader("Wechatpay-Nonce");
        String serialNo = request.getHeader("Wechatpay-Serial");
        String signature = request.getHeader("Wechatpay-Signature");

        log.info("timestamp:{} nonce:{} serialNo:{} signature:{}", timestamp, nonce, serialNo, signature);
        String result = HttpKit.readData(request);
        log.info("微信支付通知密文 {}", result);

        WechatPaymentSetting setting = wechatPaymentSetting();
        //校验服务器端响应¬
        return WxPayKit.verifyNotify(serialNo, result, signature, nonce, timestamp, setting.getApiKey3(), Objects.requireNonNull(getPlatformCert()));
    }


    /**
     * 获取微信支付配置
     *
     * @return 微信支付配置
     */
    private WechatPaymentSetting wechatPaymentSetting() {
        try {
            Setting systemSetting = settingClient.get(SettingEnum.WECHAT_PAYMENT.name());
            return JSONUtil.toBean(systemSetting.getSettingValue(), WechatPaymentSetting.class);
        } catch (Exception e) {
            log.error("微信支付暂不支持", e);
            throw new ServiceException(ResultCode.PAY_NOT_SUPPORT);
        }
    }

    /**
     * 获取平台公钥
     *
     * @return 平台公钥
     */
    private X509Certificate getPlatformCert() {
        //获取缓存中的平台公钥，如果有则直接返回，否则去微信请求
        String publicCert = cache.getString(CachePrefix.WECHAT_PLAT_FORM_CERT.getPrefix());
        if (!CharSequenceUtil.isEmpty(publicCert)) {
            return PayKit.getCertificate(publicCert);
        }
        //获取平台证书列表
        try {

            WechatPaymentSetting setting = wechatPaymentSetting();

            PaymentHttpResponse response = WechatApi.v3(RequestMethodEnums.GET, WechatDomain.CHINA.toString(),
                    WechatApiEnum.GET_CERTIFICATES.toString(), setting.getMchId(), setting.getSerialNumber(), null, setting.getApiclient_key(), "");
            String body = response.getBody();
            log.info("获取微信平台证书body: {}", body);
            if (response.getStatus() == 200) {
                JSONObject jsonObject = JSONUtil.parseObj(body);
                JSONArray dataArray = jsonObject.getJSONArray("data");
                //默认认为只有一个平台证书
                JSONObject encryptObject = dataArray.getJSONObject(0);
                JSONObject encryptCertificate = encryptObject.getJSONObject("encrypt_certificate");
                String associatedData = encryptCertificate.getStr("associated_data");
                String cipherText = encryptCertificate.getStr("ciphertext");
                String nonce = encryptCertificate.getStr("nonce");
                publicCert = getPlatformCertStr(associatedData, nonce, cipherText);
                long second = (PayKit.getCertificate(publicCert).getNotAfter().getTime() - System.currentTimeMillis()) / 1000;
                cache.put(CachePrefix.WECHAT_PLAT_FORM_CERT.getPrefix(), publicCert, second);
            } else {
                log.error("证书获取失败：{}", body);
                throw new ServiceException(ResultCode.WECHAT_CERT_ERROR);
            }
            return PayKit.getCertificate(publicCert);
        } catch (ServiceException serviceException) {
            throw serviceException;
        } catch (Exception e) {
            log.error("证书获取失败", e);
        }
        return null;
    }

    /**
     * 获取平台证书缓存的字符串
     * 下列各个密钥参数
     *
     * @param associatedData 密钥参数
     * @param nonce          密钥参数
     * @param cipherText     密钥参数
     * @return platform key
     * @throws GeneralSecurityException 密钥获取异常
     */
    private String getPlatformCertStr(String associatedData, String nonce, String cipherText) throws GeneralSecurityException {


        AesUtil aesUtil = new AesUtil(wechatPaymentSetting().getApiKey3().getBytes(StandardCharsets.UTF_8));
        //平台证书密文解密
        //encrypt_certificate 中的  associated_data nonce  ciphertext
        return aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8), nonce.getBytes(StandardCharsets.UTF_8), cipherText);
    }

    /**
     * 获取支付结果
     *
     * @param paymentWakeupParam 支付参数
     * @return 支付响应
     */
    @Transactional(rollbackFor = Exception.class)
    public String requestWechatAPI(PaymentWakeupParam paymentWakeupParam) throws Exception {
        WechatPaymentSetting setting = wechatPaymentSetting();

        // 如果支付需要合单支付，但是支付方式不支持合单支付
        if (Boolean.TRUE.equals(paymentWakeupParam.getIsCombine()) && Boolean.FALSE.equals(setting.getSupportCombine())) {
            throw new ServiceException(ResultCode.PAY_NOT_SUPPORT, "当前支付方式不支持合单支付");
        }

//        ----------------------------------------------------判定支付方式，以及接口-----------------------------------------------------

        //应用ID
        String appid;
        // 支付方式枚举
        WechatApiEnum wechatApiEnum;
        //确认支付使用的appid 和 支付方式
        switch (paymentWakeupParam.getPayParam().getPaymentClientEnum()) {
            case MP -> {
                appid = setting.getMpAppId();
                wechatApiEnum = Boolean.TRUE.equals(paymentWakeupParam.getIsCombine()) ? WechatApiEnum.COMBINE_TRANSACTIONS_JS :
                        WechatApiEnum.PARTNER_JS_API_PAY;
            }
            case NATIVE -> {
                appid = setting.getServiceAppId();
                wechatApiEnum = Boolean.TRUE.equals(paymentWakeupParam.getIsCombine()) ? WechatApiEnum.COMBINE_TRANSACTIONS_NATIVE :
                        WechatApiEnum.PARTNER_NATIVE_PAY;
            }
            case APP -> {
                appid = setting.getAppId();
                wechatApiEnum = Boolean.TRUE.equals(paymentWakeupParam.getIsCombine()) ? WechatApiEnum.COMBINE_TRANSACTIONS_APP :
                        WechatApiEnum.PARTNER_APP_PAY;
            }
            case JSAPI -> {
                appid = setting.getServiceAppId();
                wechatApiEnum = Boolean.TRUE.equals(paymentWakeupParam.getIsCombine()) ? WechatApiEnum.COMBINE_TRANSACTIONS_JS :
                        WechatApiEnum.PARTNER_JS_API_PAY;
            }
            case H5 -> {
                appid = setting.getServiceAppId();
                wechatApiEnum = Boolean.TRUE.equals(paymentWakeupParam.getIsCombine()) ? WechatApiEnum.COMBINE_TRANSACTIONS_H5 :
                        WechatApiEnum.PARTNER_H5_PAY;
            }
            default -> throw new ServiceException(ResultCode.PAY_NOT_SUPPORT);
        }


//        ----------------------------------------------------组织请求支付参数-----------------------------------------------------

        //过期时间
        String timeExpire =
                DateTimeZoneUtil.dateToTimeZone(System.currentTimeMillis() + 1000L * 60 * paymentWakeupParam.getPayParam().getTimeoutExpress());
        //附加数据
        String attach = JSONUtil.toJsonStr(new ThirdPayCallbackParam(paymentWakeupParam.getPayParam()));


        //支付响应
        PaymentHttpResponse response;
        //合单支付流程
        if (Boolean.TRUE.equals(paymentWakeupParam.getIsCombine())) {

            //合单支付请求参数
            CombineTransactionsRequest request = new CombineTransactionsRequest();
            request.setCombineAppid(appid);
            request.setCombineMchid(setting.getMchId());
            request.setCombineOutTradeNo(SnowFlake.getIdStr());
            request.setNotifyUrl(notifyUrl(apiProperties.getBuyer(), PaymentMethodEnum.WECHAT));

            request.setTimeExpire(timeExpire);

            // 如果需要支付人OPENID
            if (paymentWakeupParam.getPayParam().getPaymentClientEnum().equals(PaymentClientEnum.JSAPI) || paymentWakeupParam.getPayParam().getPaymentClientEnum().equals(PaymentClientEnum.MP)) {
                CombineTransactionsRequest.CombinePayerInfo payerInfo = new CombineTransactionsRequest.CombinePayerInfo();
                //如果需要openid却没传递，则抛出异常
                if (CharSequenceUtil.isEmpty(paymentWakeupParam.getPayParam().getOpenid())) {
                    throw new ServiceException(ResultCode.PAY_ERROR, "支付参数错误，缺少openid");
                }
                payerInfo.setOpenid(paymentWakeupParam.getPayParam().getOpenid());
                request.setCombinePayerInfo(payerInfo);
            }

            CombineTransactionsRequest.SceneInfo sceneInfo = new CombineTransactionsRequest.SceneInfo();
            sceneInfo.setPayerClientIp(IpKit.getRealIp(ThreadContextHolder.getHttpRequest()));
            request.setSceneInfo(sceneInfo);

            // 循环子订单
            for (PayParamItem paramItem : paymentWakeupParam.getPayParam().getPayParamItems()) {

                //构建金额信息
                CombineTransactionsRequest.Amount requestAmount = new CombineTransactionsRequest.Amount();
                //设置币种信息
                requestAmount.setCurrency("CNY");
                //设置金额
                requestAmount.setTotalAmount(CurrencyUtil.fen(paramItem.getPrice()));


                CombineTransactionsRequest.SubOrders subOrders = new CombineTransactionsRequest.SubOrders();


                SubMerchantInformation subMerchantInformation =
                        subMerchantInformationService.getSubMerchantInformationByUserId(paramItem.getPayeeId(), PaymentMethodEnum.WECHAT);

                if (subMerchantInformation == null) {
                    throw new ServiceException(ResultCode.PAY_ERROR, paramItem.getDescription() + "-未配置微信支付子商户号，请联系管理员");
                }

                subOrders.setMchid(setting.getMchId());

                //设置 子单商户号  mchId 和 combine_mchId 取值一样
                //如果是特殊订单，则使用平台子商户号，否则使用店铺商户号
                subOrders.setSubMchid(paramItem.getSpecialOrder() ? setting.getSubMchId() : subMerchantInformation.getSubMerchantId());


                subOrders.setAttach(attach);
                subOrders.setOutTradeNo(SnowFlake.getIdStr());

                subOrders.setDescription(paramItem.getDescription());
                subOrders.setAmount(requestAmount);
                CombineTransactionsRequest.SettleInfo settleInfo = new CombineTransactionsRequest.SettleInfo();
                settleInfo.setProfitSharing(true);
                subOrders.setSettleInfo(settleInfo);
                request.addSubOrders(subOrders);

                //填充第三方流水
                paramItem.setOutTradeNo(subOrders.getOutTradeNo());

            }

            log.info("统一下单请求 {}", JSONUtil.toJsonStr(request));


            //请求微信支付接口
            response = WechatApi.v3(RequestMethodEnums.POST, WechatDomain.CHINA.toString(), wechatApiEnum.toString(), setting.getMchId(),
                    setting.getSerialNumber(), null, setting.getApiclient_key(), GsonUtils.toJson(request));

            generateCombinePaymentLog(request, paymentWakeupParam, response.getBody());

            generatePaymentLogs(request, paymentWakeupParam);

        } else {
            // 支付参数
            PayParam payParam = paymentWakeupParam.getPayParam();

            //支付日志对象
            PaymentLog paymentLog = paymentWakeupParam.getPaymentLog();
            paymentLog.setMchId(setting.getSubMchId());


            PartnerTransactionsRequest request = new PartnerTransactionsRequest();

            request.setSpAppid(appid);
            request.setSpMchid(setting.getMchId());

//            PayParamItem payParamItem = payParam.getPayParamItems().get(0);
            //如果收款人ID为空
//            if (CharSequenceUtil.isEmpty(payParamItem.getPayeeId()) || payParamItem.getPayeeId().equals("-1")) {
//                request.setSubMchid(setting.getSubMchId());
//            } else {
//                SubMerchantInformation subMerchantInformation =
//                        subMerchantInformationService.getSubMerchantInformationByUserId(payParamItem.getPayeeId(), PaymentMethodEnum.WECHAT);
//
//                if (subMerchantInformation == null) {
//                    throw new ServiceException(ResultCode.PAY_ERROR, paymentLog.getDescription() + "-未配置微信支付子商户号，请是用其他方式支付");
//                }
//                request.setSubMchid(subMerchantInformation.getSubMerchantId());
//
//            }

            request.setSubMchid(setting.getSubMchId());
            request.setOutTradeNo(SnowFlake.getIdStr());
            request.setDescription(payParam.getTitle());

            request.setAttach(attach);
            request.setNotifyUrl(notifyUrl(apiProperties.getBuyer(), PaymentMethodEnum.WECHAT));
            request.setTimeExpire(timeExpire);

            PartnerTransactionsRequest.Amount amount = new PartnerTransactionsRequest.Amount();
            amount.setTotal(CurrencyUtil.fen(payParam.getTotalAmount()));

            request.setAmount(amount);
            PartnerTransactionsRequest.SettleInfo settleInfo = new PartnerTransactionsRequest.SettleInfo();
            settleInfo.setProfitSharing(true);
            request.setSettleInfo(settleInfo);

            // 如果需要支付人OPENID
            if (paymentWakeupParam.getPayParam().getPaymentClientEnum().equals(PaymentClientEnum.JSAPI) || paymentWakeupParam.getPayParam().getPaymentClientEnum().equals(PaymentClientEnum.MP)) {
                PartnerTransactionsRequest.Payer payer = new PartnerTransactionsRequest.Payer();
                //如果需要openid却没传递，则抛出异常
                if (CharSequenceUtil.isEmpty(paymentWakeupParam.getPayParam().getOpenid())) {
                    throw new ServiceException(ResultCode.PAY_ERROR, "支付参数错误，缺少openid");
                }
                payer.setSpOpenid(paymentWakeupParam.getPayParam().getOpenid());
                request.setPayer(payer);
            }

            PartnerTransactionsRequest.SceneInfo sceneInfo = new PartnerTransactionsRequest.SceneInfo();
            sceneInfo.setPayerClientIp(IpKit.getRealIp(ThreadContextHolder.getHttpRequest()));
            request.setSceneInfo(sceneInfo);


            paymentLog.setOutTradeNo(request.getOutTradeNo());


            log.info("统一下单请求 {}", GsonUtils.toJson(request));
            response = WechatApi.v3(RequestMethodEnums.POST, WechatDomain.CHINA.toString(), wechatApiEnum.toString(), setting.getMchId(),
                    setting.getSerialNumber(), null, setting.getApiclient_key(), GsonUtils.toJson(request));


        }


//--------------------------------------------------结果处理--------------------------------------------------
        //根据证书序列号查询对应的证书来验证签名结果
        boolean verifySignature = WxPayKit.verifySignature(response, getPlatformCert());

        log.info("统一下单响应 {}", response);


        if (!verifySignature) {
            throw new ServiceException(ResultCode.WECHAT_CERT_ERROR);
        }
        return response.getBody();
    }


    /**
     * 生成合单支付日志
     *
     * @param combineTransactionsRequest 合单支付请求参数
     * @param paymentWakeupParam         支付唤醒参数
     */
    private void generateCombinePaymentLog(CombineTransactionsRequest combineTransactionsRequest, PaymentWakeupParam paymentWakeupParam,
                                           String response) {
        CombinePaymentLog combinePaymentLog = paymentWakeupParam.getCombinePaymentLog();

        combinePaymentLog.setCombineAppid(combineTransactionsRequest.getCombineAppid());
        combinePaymentLog.setCombineMchid(combineTransactionsRequest.getCombineMchid());
        combinePaymentLog.setCombineOutTradeNo(combineTransactionsRequest.getCombineOutTradeNo());
        combinePaymentLog.setPaymentRequestSource(JSONUtil.toJsonStr(combineTransactionsRequest));
        combinePaymentLog.setPaymentResponseSource(response);
        combinePaymentLog.setOpenid(paymentWakeupParam.getPayParam().getOpenid());

    }

    /**
     * 生成支付日志
     *
     * @param combineTransactionsRequest 合单支付请求参数
     * @param paymentWakeupParam         支付唤醒参数
     */
    private void generatePaymentLogs(CombineTransactionsRequest combineTransactionsRequest, PaymentWakeupParam paymentWakeupParam) {
        PayParam payParam = paymentWakeupParam.getPayParam();
        List<PaymentLog> paymentLogs = new ArrayList<>();
        for (PayParamItem paramItem : payParam.getPayParamItems()) {
            paymentLogs.add(PaymentLog.builder().orderSn(paramItem.getSn()).paymentScene(payParam.getPaymentSceneEnums().name()).description(paramItem.getDescription()).paymentClient(payParam.getPaymentClientEnum().name()).paymentMethod(payParam.getPaymentMethodEnum().name()).price(paramItem.getPrice()).isCombine(true).combineOutTradeNo(combineTransactionsRequest.getCombineOutTradeNo()).outTradeNo(paramItem.getOutTradeNo()).timeoutExpress(DateUtil.offsetMinute(payParam.getTimeoutExpress())).payerId(UserContext.getCurrentId()).openid(payParam.getOpenid()).mchId(paramItem.getMchId()).build());
        }
        paymentWakeupParam.setPaymentLogs(paymentLogs);
    }
}
